﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Text;
#if !SILVERLIGHT
using System.Collections.Specialized;
#endif

namespace Oxage.Windows.Controls
{
	/// <summary>
	/// Command line arguments parser.<br />
	/// CodeProject article on http://www.codeproject.com/KB/recipes/command_line.aspx by GriffonRL<br />
	/// NOTE: This class needs bug fixes and unit test
	/// </summary>
	public partial class Arguments : IEnumerable
	{
		#region Private members
		private StringDictionary parameters;
		private string[] originalArray; //Original parsed command line to string array
		private string originalString; //Original command line string
		#endregion

		#region Constructors
		public Arguments(string commandLine, bool containsCommand = true)
		{
			//var args = Parse(commandLine);
			//if (containsCommand)
			//{
			//  //Skip command, only arguments are used
			//  args = args.Skip(1).ToArray();
			//}
			//Parse(args);

			//Split arguments
			//Reference: http://stackoverflow.com/questions/197233/how-to-parse-a-command-line-with-regular-expressions
			string arg = null;
			bool isFirstLoop = false;
			List<string> arguments = new List<string>();
			var matches = Regex.Matches(commandLine, @"""([^""]+)""|\s*([^""\s]+)");
			foreach (Match m in matches)
			{
				if (isFirstLoop)
				{
					isFirstLoop = false;
					continue;
				}

				arg = m.Groups[1].Value + m.Groups[2].Value;
				if (!string.IsNullOrEmpty(m.Groups[1].Value))
				{
					arguments[arguments.Count - 1] += arg;
				}
				else
				{
					arguments.Add(arg);
				}
			}

			if (containsCommand)
			{
				//Skip command, only arguments are used
				arguments = arguments.Skip(1).ToList();
			}

			Parse(arguments.ToArray());
		}

		/// <summary>
		/// Class constructor.
		/// </summary>
		/// <param name="args">List of arguments.</param>
		public Arguments(string[] args)
		{
			Parse(args);
		}
		#endregion

		#region Public properties
		/// <summary>
		/// Gets the parameter value by key.
		/// </summary>
		/// <param name="key">Argument key (e.g. for argument "-path=C:\temp" the key would be "path")</param>
		/// <returns>Returns a parameter value.</returns>
		public string this[string key]
		{
			get
			{
				return this.parameters[key];
			}
		}

		/// <summary>
		/// Gets the parameter value by index.
		/// </summary>
		/// <returns>Returns a parameter value.</returns>
		public string this[int index]
		{
			get
			{
				return this.originalArray[index];
			}
		}

		/// <summary>
		/// Gets the number of parsed arguments.
		/// </summary>
		public int Count
		{
			get
			{
				return this.originalArray.Length;
			}
		}

		/// <summary>
		/// Gets a value indicating whether arguments are missing.
		/// </summary>
		public bool Empty
		{
			get
			{
				return this.Count == 0;
			}
		}

		/// <summary>
		/// Gets a value indicating whether user requested 'help' command.
		/// </summary>
		public bool Help
		{
			get
			{
				return this.Count > 0 && (this[0] == "help" || this.Contains("help"));
			}
		}

		/// <summary>
		/// Gets concatenated arguments to string.
		/// </summary>
		public string RawArguments
		{
			get
			{
				//return this.originalString;
				return BuildRawArguments(Range(0));
			}
		}

		/// <summary>
		/// Gets the last argument from the list.
		/// </summary>
		public string LastArgument
		{
			get
			{
				return (this.Count > 0 ? this[this.Count - 1] : null);
			}
		}
		#endregion

		#region Protected methods
		protected void Parse(string[] args)
		{
			//Save original array
			this.originalArray = args;

			//Reconstruct original command line string
			var builder = new StringBuilder();
			foreach (string arg in args)
			{
				if (builder.Length > 0)
				{
					builder.Append(" ");
				}
				builder.Append(arg);
			}

			//Parse parameters
			this.parameters = new StringDictionary();
			string parameter = null;
			string[] parts = null;
			Regex spliter = new Regex(@"^-{1,2}|^/|=|:", RegexOptions.IgnoreCase /* | RegexOptions.Compiled */);
			Regex remover = new Regex(@"^['""]?(.*?)['""]?$", RegexOptions.IgnoreCase /* | RegexOptions.Compiled */);

			// Valid parameters forms:
			// {-,/,--}param{ ,=,:}((",')value(",'))
			// Examples: 
			// -param1 value1 --param2 /param3:"Test-:-work" 
			//   /param4=happy -param5 '--=nice=--'
			foreach (string arg in args)
			{
				// Look for new parameters (-,/ or --) and a
				// possible enclosed value (=,:)
				parts = spliter.Split(arg, 3);
				switch (parts.Length)
				{
					// Found a value (for the last parameter 
					// found (space separator))
					case 1:
						if (parameter != null)
						{
							if (!this.parameters.ContainsKey(parameter))
							{
								parts[0] = remover.Replace(parts[0], "$1");
								this.parameters.Add(parameter, parts[0]);
							}
							parameter = null;
						}
						// else Error: no parameter waiting for a value (skipped)
						break;

					// Found just a parameter

					case 2:
						// The last parameter is still waiting. 
						// With no value, set it to true.
						if (parameter != null)
						{
							if (!this.parameters.ContainsKey(parameter))
							{
								this.parameters.Add(parameter, "");
							}
						}
						parameter = parts[1];
						break;

					// Parameter with enclosed value
					case 3:
						// The last parameter is still waiting. 
						// With no value, set it to true.
						if (parameter != null)
						{
							if (!this.parameters.ContainsKey(parameter))
							{
								this.parameters.Add(parameter, "");
							}
						}
						parameter = parts[1];

						// Remove possible enclosing characters (",')
						if (!this.parameters.ContainsKey(parameter))
						{
							parts[2] = remover.Replace(parts[2], "$1");
							this.parameters.Add(parameter, parts[2]);
						}
						parameter = null;
						break;
				}
			}

			// In case a parameter is still waiting
			if (parameter != null && !this.parameters.ContainsKey(parameter))
			{
				this.parameters.Add(parameter, "");
			}
		}
		#endregion

		#region Public methods
		/// <summary>
		/// Check if arguments contain key.
		/// </summary>
		/// <param name="key">Argument key (e.g. for argument "-path=C:\temp" the key would be "path")</param>
		/// <returns>Return true if arguments contain key.</returns>
		public bool Contains(string key)
		{
			return this.parameters.ContainsKey(key);
		}

		/// <summary>
		/// Get the original string that has been passed to the command line.
		/// </summary>
		/// <returns>Returns command line arguments in one string.</returns>
		public override string ToString()
		{
			return this.originalString;
		}

		/// <summary>
		/// Get the list of arguments that have been passed to the command line.
		/// </summary>
		/// <returns>Returns command line arguments in array.</returns>
		public string[] ToArray()
		{
			return this.originalArray;
		}

		/// <summary>
		/// Gets a list of arguments.
		/// </summary>
		/// <param name="skip"></param>
		/// <returns></returns>
		public IEnumerable<string> Range(int skip)
		{
			//foreach (string arg in this.parameters.Values)
			foreach (string arg in this.originalArray)
			{
				if (skip-- > 0) continue;
				yield return arg;
			}
		}
		#endregion

		#region Static methods
		/// <summary>
		/// Split a single string to list of arguments.
		/// </summary>
		/// <param name="arguments">Command line arguments in a string.</param>
		/// <returns>Returns list of arguments.</returns>
		public static string[] Parse(string arguments)
		{
			string[] result = arguments.Split(' '); //TODO: Split by Regex
			return result;
		}

		/// <summary>
		/// Create an Arguments object.
		/// </summary>
		/// <param name="arguments">Command line arguments in a string.</param>
		/// <returns>Returns a new Arguments object.</returns>
		public static Arguments Create(string arguments)
		{
			string[] args = Arguments.Parse(arguments);
			Arguments result = new Arguments(args);
			return result;
		}

		/// <summary>
		/// Extracts command from a command line.
		/// </summary>
		/// <param name="commandLine"></param>
		/// <returns></returns>
		public static string GetCommand(string commandLine)
		{
			//Command cannot be determined
			if (string.IsNullOrEmpty(commandLine))
			{
				return null;
			}

			//If there is no command line arguments after the command
			if (!commandLine.Trim().Contains(" "))
			{
				return commandLine;
			}

			//Get first string until space
			var match = Regex.Match(commandLine, @"^([^\s]+)");
			if (match.Success)
			{
				return match.Groups[1].Value;
			}

			return null;
		}

		public static string BuildRawArguments(IEnumerable<string> arguments)
		{
			var builder = new StringBuilder();
			foreach (string argument in arguments)
			{
				string arg = argument;

				//Add space between arguments
				if (builder.Length > 0)
				{
					builder.Append(" ");
				}

				//Escape quotes
				arg = arg.Replace("\"", "\\");

				//Wrap in quotes
				bool mustQuote = Regex.IsMatch(arg, @"(\s)");
				if (mustQuote)
				{
					builder.AppendFormat("\"{0}\"", arg);
				}
				else
				{
					builder.Append(arg);
				}
			}
			return builder.ToString();
		}
		#endregion

		#region IEnumerable Members
		public IEnumerator GetEnumerator()
		{
			return this.parameters.GetEnumerator();
		}
		#endregion
	}

#if SILVERLIGHT
	public class StringDictionary : Dictionary<string, string>
	{
	}
#endif
}
